//  “默认配置 + 用户自定义配置” 合并
import { AxiosRequestConfig } from '../types'
import { deepMerge, isPlainObject } from '../helpers/util'

const strats = Object.create(null)  // 创建无原型的空对象，存储不同配置项的合并策略

// 1 :默认策略 defaultStrat（优先使用 config1 的值，config2 未定义时保留 config1）
function defaultStrat(val1: any, val2: any): any {
  return typeof val2 !== 'undefined' ? val2 : val1
}

// 2: 完全覆盖策略 fromVal2Strat（直接使用 config2 的值，忽略 config1）
function fromVal2Strat(val1: any, val2: any): any {
  if (typeof val2 !== 'undefined') {
    return val2  // 只要用户配置存在（非 undefined），就返回用户值
  }
}

// 3：深度合并（对象递归合并，非对象值覆盖）
function deepMergeStrat(val1: any, val2: any): any {
  if (isPlainObject(val2)) {  // 若config2的值是普通对象（如{headers: {}}）
    return deepMerge(val1, val2)
  } else if (typeof val2 !== 'undefined') { // 若config2的值不是普通对象，但存在（如字符串、数字）
    return val2// 直接用config2的值（覆盖val1）
  } else if (isPlainObject(val1)) {// 若config2未定义，且config1的值是对象
    return deepMerge(val1)
  } else {// 若config2未定义，且config1的值不是对象（如null、undefined）
    return val1
  }
}

const stratKeysFromVal2 = ['url', 'params', 'data'] // 这些配置项直接使用config2的值

stratKeysFromVal2.forEach(key => {
  strats[key] = fromVal2Strat// 分配fromVal2Strat策略（config2覆盖config1）
})

const stratKeysDeepMerge = ['headers', 'auth'] // 这些配置项可能包含嵌套对象，需深度合并

stratKeysDeepMerge.forEach(key => {
  strats[key] = deepMergeStrat // 分配deepMergeStrat策略（递归合并对象）
})


export default function mergeConfig(
  config1: AxiosRequestConfig,
  config2?: AxiosRequestConfig
): AxiosRequestConfig {
  if (!config2) {  // 若用户未传config2，初始化为空对象（避免null/undefined报错）
    config2 = {}
  }

  const config = Object.create(null)

  for (let key in config2) {  // 遍历config2的所有键（用户自定义配置）
    mergeField(key)
  }

  for (let key in config1) {
    if (!config2[key]) {// 若config2中没有该键（避免重复处理已合并的键）
      mergeField(key)
    }
  }

  function mergeField(key: string): void {
    const strat = strats[key] || defaultStrat // 获取该键对应的策略（没有则用defaultStrat）。strat 变量存储的是 函数的内存地址，因此可以像函数一样调用（strat(...)）
    config[key] = strat(config1[key], config2![key])// 执行策略，将结果存入config
  }

  return config
}
